#ifndef Matrice_h
#define Matrice_h
/**
@brief Gestion de Matrice
@author Bourgès Cyril
@date 8 juillet 2009
@date 15 décembre 2009
@date 22 janvier 2010
@date 14 octobre 2011
@version 1.6 (1.2-Ajout Push pop, 1.3-Correction, 1.4 add cross, 1.6 add namespace)
@see Site Auteur : http://shadowluz.alwaysdata.net/ \n
*/

//---STD-------------------------------------------------------------
#include <iostream>
#include <cmath>

//---Using-----------------------------------------------------------
using std::cerr;
using std::endl;

//-------------------------------------------------------------------

namespace SM {

template<class T>
class Matrice {

protected:
  ///Nombre de colonne
  unsigned int width ;
  ///Nombre de ligne
  unsigned int height ;
  ///Données
  T* data ;

public:
  /**
   * @brief matrice de taille 0 et data = NULL
   */
  Matrice ()  {
    width = 0 ;
    height = 0 ;
    data = NULL;
  }

  /**
  * @param _width : Nombre de colonne
  * @param _height : Nombre de ligne
  * @param identite : par defauld = False \n initialise une matrice identité si identité == true et que la matrice est carré
  */
  Matrice (unsigned int _width,  unsigned int _height, bool _idendite = false) {
    width = _width ;
    height = _height ;
    data = new T [width*height] ;

    if (_idendite) {
      initIdentite() ;
    }else {
      init() ;
    }
  }

  /**
  @brief copie indépendante
  */
  Matrice (const Matrice<T> &_copie) {
    width = _copie.width ;
    height = _copie.height ;
    unsigned int taille = width*height ;
    data = new T [taille] ;

    for (unsigned int i = 0 ; i < taille ; ++i) {
        data[i] = _copie.data[i] ;
    }
  }

  ~Matrice () {
    delete[] data ;
  }

  /**
  * @brief Initialise la matrice créé avec une matrice Identité
  * @return false si la matrice n'est pas carré, sinon true
  */
  bool initIdentite () {
    init() ;
  if (width==height) {
      for (unsigned int i = 0 ; i < width ; ++i) {
        data[i*width+i] = 1 ;
      }
    }else {
      return false ;
    }
    return true ;
  }

  /**
  * @brief Initialise la matrice créé à 0
  */
  void init() {
    unsigned int taille = width*height ;
    for (unsigned int i = 0 ; i < taille ; ++i) {
      data[i] = 0 ;
    }
  }

  /**
  * @brief accesseur Nombre de Colonne
  * @return nombre de colonne
  */
  unsigned int getWidth () {
    return width ;
  }

  /**
  * @brief accesseur Nombre de ligne
  * @return nombre de ligne
  */
  unsigned int getHeight () {
    return height ;
  }

  void setWidth (unsigned int _width) {
    width = _width ;
  }

  void setHeight (unsigned int _height) {
    height = _height ;
  }

  /**
  * @brief accesseur au données
  * @param _indice : indice de la matrice
  * @return reférence de la case
  */
  T& operator[](unsigned int _indice) {
    return data[_indice] ;
  }

  /**
  * @brief accesseur au données
  * @param _indice : indice 
  * @return Valeur de la case
  */
  T getData(unsigned int _indice) {
    return data[_indice] ;
  }

  /**
  * @brief accesseur au données
  * @param _indice : indice
  * @param _value : Valeur de la case
  */
  void setData(unsigned int _indice, T _value) {
    data[_indice] = _value ;
  }

  /**
  * @brief accesseur à la matrice
  * @return Pointeur de la Matrice
  */
  T* getData() {
    return data ;
  }

//   T** Matrice<T>::getPointeurData() {
//     T** retour = &data ;
//     return retour ;
//   }

  /**
    @return this si la multiplication n'a pas pu avoir lieu
  */
  Matrice<T> operator* (const Matrice<T> &_matrice) {

    if (width == _matrice.height) {
      Matrice<T> retour (_matrice.width, height) ;
      for (unsigned int i = 0 ; i < _matrice.width ; ++i) {
        for(unsigned int j = 0 ; j < height ; ++j) {
          for (unsigned int k = 0 ; k < width ; ++k) {
            retour.data[i+j*_matrice.width] += data[k+j*width] * _matrice.data[i+k*_matrice.width] ;
          }
        }
      }
      return retour ;
    }

    cerr<<"Erreur (*) : Matrice Non compatible.\n" ;
    return (*this) ;
  }

  /**
    @return this si l'addition n'a pas pu avoir lieu
  */
  Matrice<T> operator+ (const Matrice<T> &_matrice) {

    if (width==_matrice.width && height==_matrice.height) {
      unsigned int taille = width*height ;
      Matrice<T> retour  (width, height) ;
      for (unsigned int i = 0 ; i < taille ; ++i) {
        retour.data[i] = data[i] + _matrice.data[i] ;
      }
      return retour ;
    }
    
    cerr<<"Erreur (+) : Matrice Non Carre.\n" ;
    return (*this) ;
  }

  /**
    @return this si la soustraction n'a pas pu avoir lieu
  */
  Matrice<T> operator- (const Matrice<T> &_matrice) {

    if (width==_matrice.width && height==_matrice.height) {
      unsigned int taille = width*height ;
      Matrice<T> retour (width, height) ;
      for (unsigned int i = 0 ; i < taille ; ++i) {
        retour.data[i] = data[i] - _matrice.data[i] ;
      }
      return retour ;
    }

    cerr<<"Erreur (-) : Matrice De Taille Differente.\n" ;
    return (*this) ;
  }

  /**
    @return false si la multiplication n'a pas pu avoir lieu
  */
  bool operator*= (const Matrice<T> &_matrice) {

  // ----First Version----------------------------------
    Matrice<T> retour = (*this)*_matrice ;
    if (this == &retour)
      return false ;
    (*this) = retour ;
    return true ;
  }

  /**
    @return false si l'addition n'a pas pu avoir lieu
  */
  bool operator+= (const Matrice<T> &_matrice) {
  // ----First Version----------------------------------
  //   Matrice<T> retour = (*this)+_matrice ;
  //   if (this == &retour)
  //     return false ;
  //   (*this) = retour ;
  //   return true ;

  // ----Second Version---------------------------------
    if (width==_matrice.width && height==_matrice.height) {
      unsigned int taille = width*height ;
      for (unsigned int i = 0 ; i < taille ; ++i) {
        data[i] += _matrice.data[i] ;
      }
      return true ;
    }
    
    cerr<<"Erreur (+=) : Matrice De Taille Differente.\n" ;
    return false ;
  }

  /**
    @return false si la soustraction n'a pas pu avoir lieu
  */
  bool operator-= (const Matrice<T> &_matrice) {

  // ----First Version----------------------------------
  //   Matrice<T> retour = (*this)-_matrice ;
  //   if (this == &retour)
  //     return false ;
  //   (*this) = retour ;
  //   return true ;

  // ----Second Version---------------------------------
    if (width==_matrice.width && height==_matrice.height) {
      unsigned int taille = width*height ;
      for (unsigned int i = 0 ; i < taille ; ++i) {
        data[i] -= _matrice.data[i] ;
      }
      return true ;
    }
    
    cerr<<"Erreur (-=) : Matrice De Taille Differente.\n" ;
    return false ;
  }


  void operator= (const Matrice<T> &_matrice) {
    delete[] data ;
    width = _matrice.width ;
    height = _matrice.height ;
    unsigned int taille = width*height ;
    data = new T [taille] ;

    for (unsigned int i = 0 ; i < taille ; ++i) {
        data[i] = _matrice.data[i] ;
    }
  }

  /**
    @brief Transposé de la matrice
  */
  void  transpose () {
  // ----First Version----------------------------------
  //   transpose(*this) ;

  // ----Second Version---------------------------------
    Matrice<T> retour (height, width, false) ;
      for (unsigned int i = 0 ; i < width ; ++i) {
        for(unsigned int j = 0 ; j < height ; ++j)  {
          retour.data[j+i*height] = data[i+j*width]  ;
        }
      }
    (*this) = retour ;
  }

  //
  //   @brief Transpose la matrice source
  //   @param _matrice : Matrice source
  //
  // template<class T>
  // void Matrice<T>::transpose (Matrice<T> &_matrice) {
  //    Matrice<T> retour (_matrice.height, _matrice.width, false) ;
  //
  //     for (unsigned int i = 0 ; i < _matrice.width ; ++i) {
  //       for(unsigned int j = 0 ; j < _matrice.height ; ++j)  {
  //         retour.data[j+i*_matrice.height] = _matrice.data[i+j*_matrice.width]  ;
  //       }
  //     }
  //     _matrice = retour ;
  //   }

  /**
    @brief Calcul le déterminant de la matrice
  */

  double det ()  {
    if (width==height) {
      double fSomme = 0. ;
      double fProduit =1. ;
      unsigned  int x = 0 ;

      //Calcul diagonale positive
      for (unsigned int i = 0 ; i <width ; ++i)  {
        x = i ;
        //Parcours une diagonale
        for (unsigned int j = 0 ; j <height ; ++j)  {
          fProduit*=(double)data[x+j*width] ;
          ++x ;
          x = (x >= width ?x - width : x )   ;
        }
        //Ajoute produit de la diagonale
        fSomme += fProduit ;
        fProduit = 1.0 ;
      }

      //Calcul diagonale négative
      for (unsigned int i = 0 ; i <width ; ++i)  {
        x = i ;
        //Parcours une diagonale
        for (unsigned int j = 0 ; j <height ; ++j)  {
          fProduit*=(double)data[x+j*width] ;
          x = (x == 0 ? width-1 : x-1 )   ;
        }
        //Ajoute produit de la diagonale
        fSomme -= fProduit ;
        fProduit = 1.0 ;
      }
      return fSomme ;
    } else {
      //TODO Déterminant dans matrice non carré
      cerr<<"Erreur det pour Matrice non carré pas implémenté." ;
      return 0.0;
    }
  }

  /**
    @brief Calcul du produit vectoriel de vecteur 3D \n La fonction fait le produit avec les trois premier termes des matrices comme étant des vecteurs.
    @param _matrice : Second Vecteur
  */
  bool cross (Matrice<T> &_matrice) {
    Matrice<T> retour(1, 3, false) ;
    if ( (width*height) >= 3 &&
        (_matrice.width*_matrice.height) >= 3 ) {
      T termx = 0,
        termy = 0,
        termz = 0 ;
      termx = data[1] * _matrice[2] - data[2] * _matrice[1] ;
      termy = data[2] * _matrice[0] - data[0] * _matrice[2] ;
      termz = data[0] * _matrice[1] - data[1] * _matrice[0] ;
      data[0] = termx ;
      data[1] = termy ;
      data[2] = termz ;
    }else {
      cerr<<"Erreur cross : Matrice trop petite Taille (1, 3) conseillé.\n" ;
      return false ;
    }
    return true ;
  }

  /**
    @brief Calcul du produit vectoriel de vecteur 3D \n La fonction fait l'opération avec les trois premier termes des matrices comme étant des vecteurs.
    @param _matrice1 : Premier Vecteur
    @param _matrice2 : Second Vecteur
    @return Résultat vecteur ( sous forme Matrice (1,3) )
  */
  bool cross (Matrice<T> &_matrice1, Matrice<T> &_matrice2) {
    delete[] data ;
    width = 1 ;
    height = 3 ;
    unsigned int taille = width*height ;
    data = new T [taille] ;
    init() ;

    if ( (_matrice1.width*_matrice1.height) >= 3 &&
        (_matrice2.width*_matrice2.height) >= 3) {
      data[0] = _matrice1[1] * _matrice2[2] - _matrice1[2] * _matrice2[1] ;
      data[1] = _matrice1[2] * _matrice2[0] - _matrice1[0] * _matrice2[2] ;
      data[2] = _matrice1[0] * _matrice2[1] - _matrice1[1] * _matrice2[0] ;
    }else {
      cerr<<"Erreur cross : Matrice trop petite Taille (3, 1) conseillé.\n" ;
      return false ;
    }
    return true ;
  }

  /**
  @brief Normalize un vecteur 2D ou 3D \n La fonction fait l'opération avec les deux ou trois premier termes des matrices comme étant des vecteurs.
  */
  void normalize() {
    unsigned int iSize = width * height ;
    T longeur = 0 ;
    for (unsigned int i = 0 ; i < iSize ; ++i) {
      longeur += (data[i] * data[i]) ;
    }

    longeur = (T)sqrt(longeur) ;
    if (longeur==0) {
      longeur = 1 ;
    }
    for (unsigned int i = 0 ; i < iSize ; ++i) {
      data[i] /= longeur ;
    }
  }

  void affiche () {
    std::cout<<"--------------------------------\n" ;
    for (unsigned int i=0 ; i<width ; ++i) {
      for (unsigned int j=0 ; j<height ; ++j) {
        std::cout<<data[i+j*width]<<" | " ;
      }
      std::cout<<std::endl ;
    }
    std::cout<<"--------------------------------\n" ;
  }

  //
  //   @brief Calcul le déterminant de la matrice source
  //   @param _matrice : Matrice source
  //
  // template<class T>
  // double Matrice<T>::det (Matrice<T> &_matrice) {
  //   if (_matrice.width==_matrice.height) {
  //     double fSomme = 0. ;
  //     double fProduit =1. ;
  //     unsigned  int x = 0 ;
  //
  //     //Calcul diagonale positive
  //     for (unsigned int i = 0 ; i <_matrice.width ; ++i)  {
  //       x = i ;
  //       //Parcours une diagonale
  //       for (unsigned int j = 0 ; j <_matrice.height ; ++j)  {
  //         fProduit*=(double)_matrice.data[x+j*_matrice.width] ;
  //         ++x ;
  //         x = (x >= _matrice.width ?x - _matrice.width : x )   ;
  //       }
  //       //Ajoute produit de la diagonale
  //       fSomme += fProduit ;
  //       fProduit = 1.0 ;
  //     }
  //
  //     //Calcul diagonale négative
  //     for (unsigned int i = 0 ; i <_matrice.width ; ++i)  {
  //       x = i ;
  //       //Parcours une diagonale
  //       for (unsigned int j = 0 ; j <_matrice.height ; ++j)  {
  //         fProduit*=(double)_matrice.data[x+j*_matrice.width] ;
  //         x = (x == 0 ? _matrice.width-1 : x-1 )   ;
  //       }
  //       //Ajoute produit de la diagonale
  //       fSomme -= fProduit ;
  //       fProduit = 1.0 ;
  //     }
  //     return fSomme ;
  //   } else {
  //     //TODO Déterminant dans matrice non carré
  //     cerr<<"Erreur det pour Matrice non carré pas implémenté." ;
  //     return 0.0;
  //   }
  // }
};

}

#endif


